package choreganizer;

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.prefs.*;

import java.util.zip.*;

public class DateTable
extends JTable
implements MouseMotionListener
{

	private DateTableModel dtm;

	public DateTable()
	{
		dtm = new DateTableModel();
		
		setModel( dtm );
		setRowHeight( 120 );
		getTableHeader().setReorderingAllowed( false );
		setDefaultRenderer( DatePanel.class, new DateTableCellRenderer() );
		setDefaultEditor( DatePanel.class, new DateTableCellEditor() );
		
		//addMouseMotionListener(this);
	}
	
	public void initialize(int userMonth, int userYear)
	{
		// We'll clear the table if there's an error reading in the
		// database file, so there's no mistake that something bad's happened
		try {
			dtm.initialize(userMonth,userYear);
		} catch (Exception e) {
			// WE SHOULD SET UP A PROPERTY LISTENER TO DISABLE STUFF IN CHOREGANIZER
			// WHEN WE DECIDE TO CLEAR
			dtm.clear();
			System.err.println("Error in DateTableModel.initialize()");
			e.printStackTrace(System.err);
		}
	}
	
	public ChoreSchedule[] getCurrentDayItem(int userMonth, int userYear)
	{
		return dtm.getCurrentDayItem(userMonth, userYear);
	}
	
	public void setDatabase(File databaseFile)
	{
		dtm.setDatabase(databaseFile);
	}
	
	public void mouseDragged(MouseEvent e)
	{
	}
	
	public void mouseMoved(MouseEvent e) 
	{
		Point p = e.getPoint();
		int colAtPoint = columnAtPoint(p);
		int rowAtPoint = rowAtPoint(p);
		
		if (colAtPoint != -1 && rowAtPoint != -1)
			editCellAt(rowAtPoint,colAtPoint);
	}
	
}


class DateTableCellRenderer 
implements TableCellRenderer
{
	public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
	{
		// changes border color of the MyDatePanel for the selected day
		if (value != null)
		{
			DatePanel mdp = (DatePanel)value;
			mdp.setBorder( BorderFactory.createEmptyBorder(1,1,1,1) );
			mdp.scrollPaneVisible( true );
		}
		
		return (DatePanel)value;

	}
}


class DateTableCellEditor 
extends AbstractCellEditor 
implements TableCellEditor
{
	private Object MyVal;
	
	public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
	{
		MyVal = value;
		
		if (value != null)
		{
			DatePanel mdp = (DatePanel)value;
			
			if ( !mdp.isPlaceholder() )
			{
				mdp.setBorder( BorderFactory.createLineBorder(Color.black,1) );
				mdp.scrollPaneVisible( false );
			}
		}
		
		return (DatePanel)value;
	}
	
	public Object getCellEditorValue() 
	{
		return MyVal;
	}
	
}


class DatePanel
extends JPanel
{
	private static Preferences pref;
	private static int currDay = 0;
	
	private int day;
	private boolean isPlaceholder;
	private boolean isToday;
	private JScrollPane jsp;
	private JPanel chorePanel;
	private JLabel dayOfMonth;
	
	private Color iphColor = new Color( getBackground().getRed()-13, getBackground().getGreen()-13, getBackground().getBlue()-13 );
	private Color inphColor = new Color( getBackground().getRed()-80, getBackground().getGreen()-80, getBackground().getBlue()-80 );
	
	
	public DatePanel(int day, boolean iph)
	{
		setLayout( new BorderLayout() );
		
		isToday = false;
		this.day = day;
		
		chorePanel = new JPanel();
		chorePanel.setOpaque(false);
		
		// Set up a label to display the date (if there is one)
		dayOfMonth = new JLabel( Integer.toString(day), JLabel.RIGHT );
		Font lFont = new Font( dayOfMonth.getFont().getName(), Font.BOLD, 16 );
		dayOfMonth.setFont( lFont );
		dayOfMonth.setForeground( inphColor );
		dayOfMonth.setVerticalTextPosition( JLabel.TOP );

		// Create a seperate panel for our chores
		chorePanel.setLayout( new BoxLayout(chorePanel,BoxLayout.Y_AXIS) );
		//jsp = new JScrollPane( chorePanel );
		jsp = new JScrollPane();
		jsp.setViewportView(chorePanel);
		jsp.setOpaque(false);
		jsp.setBorder( BorderFactory.createEmptyBorder(0,0,0,0) );
		jsp.getViewport().setOpaque(false);
		
		add( dayOfMonth, BorderLayout.NORTH );
		//add( chorePanel, BorderLayout.CENTER );
		add( jsp, BorderLayout.CENTER );

		setPlaceholder(iph);
	}
	
	public static void setCurrentDay(int i)
	{
		currDay = i;
	}
	
	public void setPlaceholder(boolean b)
	{
		isPlaceholder = b;
		if(b)
		{
			removeAll();
			setBackground(iphColor);
		}
	}

	public boolean isPlaceholder()
	{
		return isPlaceholder;
	}

	public void setToday(boolean b)
	{
		isToday = b;
		if (b)
		{
			dayOfMonth.setForeground( Color.RED );
		}
	}
	
	public boolean isToday()
	{
		return isToday;
	}
	
	public int getDate()
	{
		return day;
	}
	
	public void addChoreSchedule(ChoreSchedule cs)
	{
		// Pad it a few pixels, then add it
		chorePanel.add( Box.createRigidArea(new Dimension(6, 4)) );
		cs.setAlignmentX( Component.LEFT_ALIGNMENT );
		chorePanel.add( cs );
	}
	
	public Component[] getChorePanelComponents()
	{
		return chorePanel.getComponents();
	}
	
	public void scrollPaneVisible(boolean b)
	{
		if (b)
		{
			jsp.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER );
			jsp.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
		}
		else
		{
			jsp.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED );
			jsp.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		}
	}
	
	/*
	* draw an x across the panel if it's before the current day
	*/
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		
		// Since we actually delete the preference file in Choreganizer when we
		// reset it, we should get an instance each time we repaint, otherwise we
		// might repaint during a time when it's deleted and get random errors
		// If we switch back to just clearing it, we can probably just instantiate
		// this in the constructor once
		pref = Preferences.userNodeForPackage( this.getClass() );
		if (pref != null)
		{
			boolean paintCross = pref.getBoolean("CrossOutOldDays",false);
			if (!isPlaceholder && day < currDay && paintCross)
			{
				Color s = getBackground();
				
				Graphics2D g2d = (Graphics2D)g;
				Stroke orig = g2d.getStroke();
				Dimension size = getSize();
				
				g2d.setStroke(new BasicStroke(12.0f));
				g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				g2d.setColor( new Color(s.getRed()-10, s.getGreen()-10, s.getBlue()-10) );
				g2d.drawLine(0,0,size.width,size.height);
				g2d.drawLine(size.width,0,0,size.height);
				g2d.setStroke(orig);
			}
		}
	}
}


class DateTableModel 
extends AbstractTableModel
{
	private File databaseFile;
	private Object[][] modelData = {};
	
	private static final String days[] = {
			"Sunday", "Monday", "Tuesday", "Wednesday", 
			"Thursday", "Friday", "Saturday" };

	
	/*
	* Empty default constructor
	* 
	*/
	public DateTableModel()
	{
	}
	
	
	/*
	* Sets the database file, which holds the information
	* for out table
	*/
	public void setDatabase(File databaseFile)
	{
		this.databaseFile = databaseFile;
	}
	
	
	/*
	*
	* Gets the current day, finds the row/column that holds the panel
	* for that day, finds any/all ChoreSchedule items it holds, and
	* returns them
	*/
	public ChoreSchedule[] getCurrentDayItem(int userMonth, int userYear)
	{
		// Create a vector to hold our items
		java.util.List<ChoreSchedule> choreSchedules = 
			new java.util.Vector<ChoreSchedule>();
				
		GregorianCalendar today = new GregorianCalendar( userYear,userMonth,1 );
		int firstOfMonth = today.get( Calendar.DAY_OF_WEEK );
		today = new GregorianCalendar();
		
		// If the calendar should be active...
		if (today.get(Calendar.MONTH) == userMonth && today.get(Calendar.YEAR) == userYear)
		{
			// Find out what row/column holds the panel for the current day
			int todaysDayOfWeek = today.get( Calendar.DAY_OF_WEEK );
			int todaysDayOfMonth = today.get(Calendar.DAY_OF_MONTH);
			
			int numDays = today.getActualMaximum( Calendar.DAY_OF_MONTH );
			int numRows = (firstOfMonth+numDays <= 35 ? 5 : 6);
			
			int column = todaysDayOfWeek - 1;
			int row = todaysDayOfMonth / 7;
			
			// Get all the ChoreSchedule items in that panel
			DatePanel dp = (DatePanel)getValueAt(row,column);
			Component[] comps = dp.getChorePanelComponents();
			
			for (int i=0; i < comps.length; i++)
			{
				if (comps[i] instanceof ChoreSchedule)
				{
					ChoreSchedule item = (ChoreSchedule)comps[i];
					choreSchedules.add(item);
				}
			}
		}
			
		today = null;
		return choreSchedules.toArray(new ChoreSchedule[0]);
	}
	
	
	/*
	*
	* Clears the table model.  We call this when we've had an error readin
	* in our database file, so the person knows they need a new file
	*
	*/
	public void clear()
	{
		modelData = new Object[0][0];
	}
	
	
	/*
	* Creates our table
	*
	*/
	synchronized public void initialize(int userMonth, int userYear)
	throws IOException, ZipException
	{
		GregorianCalendar calendar = new GregorianCalendar( userYear,userMonth,1 );
		int firstOfMonth = calendar.get( Calendar.DAY_OF_WEEK )-1;
		int numDays = calendar.getActualMaximum( Calendar.DAY_OF_MONTH );
		int numRows = (firstOfMonth+numDays <= 35 ? 5 : 6);
		
		
		GregorianCalendar today = new GregorianCalendar();
		boolean isCurrYearMonth = false;
		int todaysDayOfMonth = 0;
		if (today.get(Calendar.MONTH) == userMonth && today.get(Calendar.YEAR) == userYear)
		{
			isCurrYearMonth = true;
			todaysDayOfMonth = today.get(Calendar.DAY_OF_MONTH);
		}
		
		DatePanel.setCurrentDay( todaysDayOfMonth );
		
		// Get an array listing all the
		MyReaders mRead = new MyReaders( databaseFile );
		ChoreSchedule[] chores = mRead.getChoreSchedules( userMonth, userYear );

		modelData = new Object[numRows][7];
		DatePanel mdp;
		
		// Populate our table
		int currDate = 1;
		for (int i=0; i < numRows*7; i++)
		{
			int week = (int)(i/7);	// the quotient = the week
			int day = (int)(i%7);	// the remainder = the day
			
			// This panel is for an actual day
			if ( i >= firstOfMonth && i < numDays+firstOfMonth )
			{
				mdp = new DatePanel( currDate, false );
				
				for ( ChoreSchedule cs: chores )
				{
					// If this Chore happens on this day of the month
					if ( cs.hasDayOfMonth(currDate) )
						// Can't add multiple items to the same container, so clone it then add it
						mdp.addChoreSchedule( (ChoreSchedule)cs.clone() );
				}
				
				// This DatePanel falls on todays actual month/day/year
				if (isCurrYearMonth && currDate == todaysDayOfMonth)
					mdp.setToday(true);
				
				modelData[week][day] = mdp;
				currDate++;
			}
			// This panel is a spacer, and not a real day
			else
			{
				mdp = new DatePanel(0,true);
				modelData[week][day] = mdp;
			}

		}
		
		// Cleanup
		calendar = null;
		today = null;
	}

	
	/*
	* Name:		getColumnCount()
	* Params:	none
	* Return:	int
	* Desc:		Overidden Method
	*				Returns the number of columns in our table
	*/
	public int getColumnCount() 
	{
		if (modelData.length > 0)
			return days.length;
		else
			return 0;
	}
	
	
	/*
	* Name:		getRowCount()
	* Params:	none
	* Return:	int
	* Desc:		Overidden Method
	*				Returns the number of rows in our table
	*/
	public int getRowCount() 
	{
		return modelData.length;
	}
	
	
	/*
	* Name:		getColumnName()
	* Params:	int col - column of table
	* Return:	String
	* Desc:		Overidden Method
	*				Returns the header name for the given column
	*/
	public String getColumnName(int col) 
	{
		if (modelData.length > 0)
			return days[col];
		else
			return "";
	}
	
	
	/*
	* Name:		getValueAt()
	* Params:	int row - row in the table
	*				int col - column in the table
	* Return:	Object
	* Desc:		Overidden Method
	*				Returns the object (it'll be a panel) in the cell at
	*				the given row/column of the table
	*/
	public Object getValueAt(int row, int col) 
	{
		return modelData[row][col];
	}
	
	
	/*
	* Name:		getColumnClass
	* Params:	int cell - a cell in the table
	* Return:	Class
	* Desc:		Overidden Method
	*				Finds the class (CellRenderer) used for cell in our
	*				JTable, and returns it back to the caller
	*/
	public Class getColumnClass(int cell) 
	{
		// We'll set our return class to a basic JPanel
		Class c = JPanel.class;
		
		// Attempt to find the class of the renderer used for cell
		// while catching any errors (like cell == null)
		try 
		{
			c = getValueAt(0,cell).getClass();
		} 
		
		// Whatever happens, return our class c
		finally 
		{
			return c;
		}
	}
	
	
	/*
	* Name:		isCellEditable()
	* Params:	int row - row of table
	*				int cold - column of table
	* Return:	boolean
	* Desc:		Overidden Method
	*				Finds the cell at given row/column, and determines if the
	*				panel is one of our custom panels, or a placeholder panel.  If
	*				it's one of our custom panels, return true to indicate it can be
	*				edited.  Otherwise, return false.
	*/
	public boolean isCellEditable(int row, int col) 
	{
		if (getValueAt(row,col) instanceof DatePanel)
		{
			DatePanel mdp = (DatePanel)getValueAt(row,col);
			
			if ( !mdp.isPlaceholder() )
			{
				return true;
			}
		}
		
		return false;
	}
	
}